﻿
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;
using System.Text.RegularExpressions;
using System.Configuration;
using InfoPick.Lib.Tools;

namespace InfoPick.Lib.Telnet
{
    enum Verbs
    {
        WILL = 251,
        WONT = 252,
        DO = 253,
        DONT = 254,
        IAC = 255
    }

    enum Options
    {
        SGA = 3
    }

    /// <summary>
    /// 封装telnet基本操作
    /// conceived by Tom Janssens on 2007/06/06  for codeproject
    /// edit by shark on 2012/09/13
    /// </summary>
    public class TelnetConnection
    {
        TcpClient tcpSocket;
        string hostname;
        int port;

        private static readonly int TimeOutMs = int.Parse(ConfigurationManager.AppSettings["TelnetTimeout"]);

       

        public TelnetConnection(string Hostname, int Port)
        {
            tcpSocket = new TcpClient();
            tcpSocket.ReceiveBufferSize = 1024 * 1024 * 2;  //2M
            this.hostname = Hostname;
            this.port = Port;
        }

        public void Connect()
        {
            tcpSocket.Connect(hostname, port);
        }

        public void Close()
        {
            lock (lockObj)
            {
                tcpSocket.Close();
            }
        }


        public string Login(string Username, string Password)
        {

            string s = Read(new string[] { ":" });
            if (!s.TrimEnd().EndsWith(":"))
                throw new Exception("连接失败 : no login prompt");
            WriteLine(Username);

            s += Read(new string[] { ":" });
            if (!s.TrimEnd().EndsWith(":"))
                throw new Exception("连接失败 : no password prompt");
            WriteLine(Password);

            if (Username.ToLower() == "root")
                s += Read(new string[] { "#" });
            else
                s += Read(new string[] { "$" });
            return s;
        }


        private object lockObj = new object();
        /// <summary>
        /// 执行一条命令
        /// </summary>
        /// <param name="cmd">命令</param>
        /// <returns>返回的文本</returns>
        public string ExecuteCmd(string cmd, string[] endIdentifiers)
        {
            if (!tcpSocket.Connected)
                throw new Exception("没有连接到主机或已断开连接!");
            lock (lockObj)
            {
                WriteLine(cmd);
                string res = Read(endIdentifiers);
                res = res.Replace("\r\0\r\n", "\r\n");
                res = res.Replace("\r\n\n", "\r\n");
                return res;
            }
        }

        public void WriteLine(string cmd)
        {
            Write(cmd + "\n");
        }

        /// <summary>
        /// 输入命令
        /// </summary>
        /// <param name="cmd"></param>
        public void Write(string cmd)
        {
            if (!tcpSocket.Connected) return;
            byte[] buf = TelnetEnCoding.GetBytes(cmd.Replace("\0xFF", "\0xFF\0xFF"));
            tcpSocket.GetStream().Write(buf, 0, buf.Length);
        }

        /// <summary>
        /// 读取返回字符串
        /// </summary>
        /// <param name="endIdentifiers">
        /// 结束标示符，有先后关系
        /// 数组中索引越大的标示符在返回字符串中的位置索引也越大
        /// </param>
        /// <returns></returns>
        public string Read(string[] endIdentifiers)
        {
            if (!tcpSocket.Connected) return null;
            //因为要判断结束标示符，这里不能用StringBuilder，效率有所下降
            //StringBuilder sb = new StringBuilder();

            string sb = string.Empty;

            //重复到超时
            int timeoutTotal = 0;
            bool isComplete = false;
            while (!isComplete)
            {
                if (timeoutTotal > TimeOutMs)
                    throw new Exception(string.Format("读取数据超时或返回格式错误\r\n{0}\r\n", sb));
                var str = ParseTelnet();

                sb += str;

                isComplete = StringTools.CheckIsComplete(sb, endIdentifiers);
               
                Thread.Sleep(100);
                timeoutTotal += 100;
            }
            return sb;
        }

        

        /// <summary>
        /// 监测字符串是否全部读取
        /// </summary>
        /// <param name="sb"></param>
        /// <param name="reg"></param>
        /// <returns></returns>
        private bool CheckIsComplete(string sb, Regex reg)
        {
            return reg.IsMatch(sb);
        }

        public bool IsConnected
        {
            get { return tcpSocket.Connected; }
        }

        string ParseTelnet()
        {
            List<byte> bs = new List<byte>();
            while (tcpSocket.Available > 0)
            {
                int input = tcpSocket.GetStream().ReadByte();
                switch (input)
                {
                    case -1:
                        break;
                    case (int)Verbs.IAC:
                        // interpret as command
                        int inputverb = tcpSocket.GetStream().ReadByte();
                        if (inputverb == -1) break;
                        switch (inputverb)
                        {
                            case (int)Verbs.IAC:
                                //literal IAC = 255 escaped, so append char 255 to string
                                bs.Add((byte)inputverb);
                                break;
                            case (int)Verbs.DO:
                            case (int)Verbs.DONT:
                            case (int)Verbs.WILL:
                            case (int)Verbs.WONT:
                                // reply to all commands with "WONT", unless it is SGA (suppres go ahead)
                                int inputoption = tcpSocket.GetStream().ReadByte();
                                if (inputoption == -1) break;
                                tcpSocket.GetStream().WriteByte((byte)Verbs.IAC);
                                if (inputoption == (int)Options.SGA)
                                    tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WILL : (byte)Verbs.DO);
                                else
                                    tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WONT : (byte)Verbs.DONT);
                                tcpSocket.GetStream().WriteByte((byte)inputoption);
                                break;
                            default:
                                break;
                        }
                        break;
                    default:
                        bs.Add((byte)input);
                        break;
                }
            }

            string str = TelnetEnCoding.GetString(bs.ToArray());
            return str;
            
        }

        private static readonly string telnetEncoding = ConfigurationManager.AppSettings["TelnetEncoding"];
        private static Encoding TelnetEnCoding
        {
            get
            {
                try
                {
                    var code = Encoding.GetEncoding(telnetEncoding);
                    if (code != null)
                        return code;
                    return Encoding.UTF8;
                }
                catch
                {
                    return Encoding.UTF8;
                }
            }
        }
    }
}
